package com.vst.orm.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import com.vst.orm.search.Field;
import com.vst.orm.search.Filter;
import com.vst.orm.search.IMutableSearch;
import com.vst.orm.search.ISearch;
import com.vst.orm.search.Search;
import com.vst.orm.search.Sort;

public class SearchUtil {
	public static void addFetch(IMutableSearch search, String property) {
		if ((property == null) || ("".equals(property))) {
			return;
		}
		List<String> fetches = search.getFetches();
		if (fetches == null) {
			fetches = new ArrayList();
			search.setFetches(fetches);
		}
		fetches.add(property);
	}

	public static void addFetches(IMutableSearch search, String... properties) {
		if (properties != null) {
			for (String property : properties) {
				addFetch(search, property);
			}
		}
	}

	public static void addField(IMutableSearch search, Field field) {
		List<Field> fields = search.getFields();
		if (fields == null) {
			fields = new ArrayList();
			search.setFields(fields);
		}
		fields.add(field);
	}

	public static void addFields(IMutableSearch search, Field... fields) {
		if (fields != null) {
			for (Field field : fields) {
				addField(search, field);
			}
		}
	}

	public static void addField(IMutableSearch search, String property) {
		if (property == null) {
			return;
		}
		addField(search, new Field(property));
	}

	public static void addField(IMutableSearch search, String property, int operator) {
		if (property == null) {
			return;
		}
		addField(search, new Field(property, operator));
	}

	public static void addField(IMutableSearch search, String property, int operator, String key) {
		if ((property == null) || (key == null)) {
			return;
		}
		addField(search, new Field(property, operator, key));
	}

	public static void addField(IMutableSearch search, String property, String key) {
		if ((property == null) || (key == null)) {
			return;
		}
		addField(search, new Field(property, key));
	}

	public static void addFilter(IMutableSearch search, Filter filter) {
		List<Filter> filters = search.getFilters();
		if (filters == null) {
			filters = new ArrayList();
			search.setFilters(filters);
		}
		filters.add(filter);
	}

	public static void addFilters(IMutableSearch search, Filter... filters) {
		if (filters != null) {
			for (Filter filter : filters) {
				addFilter(search, filter);
			}
		}
	}

	public static void addFilterAll(IMutableSearch search, String property, Filter filter) {
		addFilter(search, Filter.all(property, filter));
	}

	public static void addFilterAnd(IMutableSearch search, Filter... filters) {
		addFilter(search, Filter.and(filters));
	}

	public static void addFilterEmpty(IMutableSearch search, String property) {
		addFilter(search, Filter.isEmpty(property));
	}

	public static void addFilterEqual(IMutableSearch search, String property, Object value) {
		addFilter(search, Filter.equal(property, value));
	}

	public static void addFilterGreaterOrEqual(IMutableSearch search, String property, Object value) {
		addFilter(search, Filter.greaterOrEqual(property, value));
	}

	public static void addFilterGreaterThan(IMutableSearch search, String property, Object value) {
		addFilter(search, Filter.greaterThan(property, value));
	}

	public static void addFilterILike(IMutableSearch search, String property, String value) {
		addFilter(search, Filter.ilike(property, value));
	}

	public static void addFilterIn(IMutableSearch search, String property, Collection<?> value) {
		addFilter(search, Filter.in(property, value));
	}

	public static void addFilterIn(IMutableSearch search, String property, Object... value) {
		addFilter(search, Filter.in(property, value));
	}

	public static void addFilterLessOrEqual(IMutableSearch search, String property, Object value) {
		addFilter(search, Filter.lessOrEqual(property, value));
	}

	public static void addFilterLessThan(IMutableSearch search, String property, Object value) {
		addFilter(search, Filter.lessThan(property, value));
	}

	public static void addFilterLike(IMutableSearch search, String property, String value) {
		addFilter(search, Filter.like(property, value));
	}

	public static void addFilterNone(IMutableSearch search, String property, Filter filter) {
		addFilter(search, Filter.none(property, filter));
	}

	public static void addFilterNot(IMutableSearch search, Filter filter) {
		addFilter(search, Filter.not(filter));
	}

	public static void addFilterNotEqual(IMutableSearch search, String property, Object value) {
		addFilter(search, Filter.notEqual(property, value));
	}

	public static void addFilterNotIn(IMutableSearch search, String property, Collection<?> value) {
		addFilter(search, Filter.notIn(property, value));
	}

	public static void addFilterNotIn(IMutableSearch search, String property, Object... value) {
		addFilter(search, Filter.notIn(property, value));
	}

	public static void addFilterNotEmpty(IMutableSearch search, String property) {
		addFilter(search, Filter.isNotEmpty(property));
	}

	public static void addFilterNotNull(IMutableSearch search, String property) {
		addFilter(search, Filter.isNotNull(property));
	}

	public static void addFilterNull(IMutableSearch search, String property) {
		addFilter(search, Filter.isNull(property));
	}

	public static void addFilterOr(IMutableSearch search, Filter... filters) {
		addFilter(search, Filter.or(filters));
	}

	public static void addFilterSome(IMutableSearch search, String property, Filter filter) {
		addFilter(search, Filter.some(property, filter));
	}

	public static void addSort(IMutableSearch search, Sort sort) {
		if (sort == null) {
			return;
		}
		List<Sort> sorts = search.getSorts();
		if (sorts == null) {
			sorts = new ArrayList();
			search.setSorts(sorts);
		}
		sorts.add(sort);
	}

	public static void addSorts(IMutableSearch search, Sort... sorts) {
		if (sorts != null) {
			for (Sort sort : sorts) {
				addSort(search, sort);
			}
		}
	}

	public static void addSort(IMutableSearch search, String property, boolean desc) {
		addSort(search, property, desc, false);
	}

	public static void addSort(IMutableSearch search, String property, boolean desc, boolean ignoreCase) {
		if (property == null) {
			return;
		}
		addSort(search, new Sort(property, desc, ignoreCase));
	}

	public static void addSortAsc(IMutableSearch search, String property) {
		addSort(search, property, false, false);
	}

	public static void addSortAsc(IMutableSearch search, String property, boolean ignoreCase) {
		addSort(search, property, false, ignoreCase);
	}

	public static void addSortDesc(IMutableSearch search, String property) {
		addSort(search, property, true, false);
	}

	public static void addSortDesc(IMutableSearch search, String property, boolean ignoreCase) {
		addSort(search, property, true, ignoreCase);
	}

	public static void removeFetch(IMutableSearch search, String property) {
		if (search.getFetches() != null) {
			search.getFetches().remove(property);
		}
	}

	public static void removeField(IMutableSearch search, Field field) {
		if (search.getFields() != null) {
			search.getFields().remove(field);
		}
	}

	public static void removeField(IMutableSearch search, String property) {
		if (search.getFields() == null) {
			return;
		}
		Iterator<Field> itr = search.getFields().iterator();
		while (itr.hasNext()) {
			if (((Field) itr.next()).getProperty().equals(property)) {
				itr.remove();
			}
		}
	}

	public static void removeField(IMutableSearch search, String property, String key) {
		if (search.getFields() == null) {
			return;
		}
		Iterator<Field> itr = search.getFields().iterator();
		while (itr.hasNext()) {
			Field field = (Field) itr.next();
			if ((field.getProperty().equals(property)) && (field.getKey().equals(key))) {
				itr.remove();
			}
		}
	}

	public static void removeFilter(IMutableSearch search, Filter filter) {
		List<Filter> filters = search.getFilters();
		if (filters != null) {
			filters.remove(filter);
		}
	}

	public static void removeFiltersOnProperty(IMutableSearch search, String property) {
		if ((property == null) || (search.getFilters() == null)) {
			return;
		}
		Iterator<Filter> itr = search.getFilters().iterator();
		while (itr.hasNext()) {
			if (property.equals(((Filter) itr.next()).getProperty())) {
				itr.remove();
			}
		}
	}

	public static void removeSort(IMutableSearch search, Sort sort) {
		if (search.getSorts() != null) {
			search.getSorts().remove(sort);
		}
	}

	public static void removeSort(IMutableSearch search, String property) {
		if ((property == null) || (search.getSorts() == null)) {
			return;
		}
		Iterator<Sort> itr = search.getSorts().iterator();
		while (itr.hasNext()) {
			if (property.equals(((Sort) itr.next()).getProperty())) {
				itr.remove();
			}
		}
	}

	public static void clear(IMutableSearch search) {
		clearFilters(search);
		clearSorts(search);
		clearFields(search);
		clearPaging(search);
		clearFetches(search);
		search.setResultMode(0);
		search.setDisjunction(false);
	}

	public static void clearFetches(IMutableSearch search) {
		if (search.getFetches() != null) {
			search.getFetches().clear();
		}
	}

	public static void clearFields(IMutableSearch search) {
		if (search.getFields() != null) {
			search.getFields().clear();
		}
	}

	public static void clearFilters(IMutableSearch search) {
		if (search.getFilters() != null) {
			search.getFilters().clear();
		}
	}

	public static void clearPaging(IMutableSearch search) {
		search.setFirstResult(-1);
		search.setPage(-1);
		search.setMaxResults(-1);
	}

	public static void clearSorts(IMutableSearch search) {
		if (search.getSorts() != null) {
			search.getSorts().clear();
		}
	}

	public static void mergeSortsBefore(IMutableSearch search, List<Sort> sorts) {
		List<Sort> list = search.getSorts();
		if (list == null) {
			list = new ArrayList();
			search.setSorts(list);
		}
		if (list.size() > 0) {
			Iterator<Sort> itr = list.iterator();
			String property;
			while (itr.hasNext()) {
				property = ((Sort) itr.next()).getProperty();
				if (property == null) {
					itr.remove();
				} else {
					for (Sort sort : sorts) {
						if (property.equals(sort.getProperty())) {
							itr.remove();
							break;
						}
					}
				}
			}
		}
		list.addAll(0, sorts);
	}

	public static void mergeSortsBefore(IMutableSearch search, Sort... sorts) {
		mergeSortsBefore(search, Arrays.asList(sorts));
	}

	public static void mergeSortsAfter(IMutableSearch search, List<Sort> sorts) {
		List<Sort> list = search.getSorts();
		if (list == null) {
			list = new ArrayList();
			search.setSorts(list);
		}
		int origLen = list.size();
		if (origLen > 0) {
			for (Sort sort : sorts) {
				if (sort.getProperty() != null) {
					boolean found = false;
					for (int i = 0; i < origLen; i++) {
						if (sort.getProperty().equals(((Sort) list.get(i)).getProperty())) {
							found = true;
							break;
						}
					}
					if (!found) {
						list.add(sort);
					}
				}
			}
		} else {
			list.addAll(sorts);
		}
	}

	public static void mergeSortsAfter(IMutableSearch search, Sort... sorts) {
		mergeSortsAfter(search, Arrays.asList(sorts));
	}

	public static void mergeFetches(IMutableSearch search, List<String> fetches) {
		List<String> list = search.getFetches();
		if (list == null) {
			list = new ArrayList();
			search.setFetches(list);
		}
		for (String fetch : fetches) {
			if (!list.contains(fetch)) {
				list.add(fetch);
			}
		}
	}

	public static void mergeFetches(IMutableSearch search, String... fetches) {
		mergeFetches(search, Arrays.asList(fetches));
	}

	public static void mergeFiltersAnd(IMutableSearch search, List<Filter> filters) {
		List<Filter> list = search.getFilters();
		if (list == null) {
			list = new ArrayList();
			search.setFilters(list);
		}
		if ((list.size() == 0) || (!search.isDisjunction())) {
			search.setDisjunction(false);
			list.addAll(filters);
		} else {
			search.setFilters(new ArrayList());

			Filter orFilter = Filter.or(new Filter[0]);
			orFilter.setValue(list);
			addFilter(search, orFilter);

			search.setDisjunction(false);
			search.getFilters().addAll(filters);
		}
	}

	public static void mergeFiltersAnd(IMutableSearch search, Filter... filters) {
		mergeFiltersAnd(search, Arrays.asList(filters));
	}

	public static void mergeFiltersOr(IMutableSearch search, List<Filter> filters) {
		List<Filter> list = search.getFilters();
		if (list == null) {
			list = new ArrayList();
			search.setFilters(list);
		}
		if ((list.size() == 0) || (search.isDisjunction())) {
			search.setDisjunction(true);
			list.addAll(filters);
		} else {
			search.setFilters(new ArrayList());

			Filter orFilter = Filter.and(new Filter[0]);
			orFilter.setValue(list);
			addFilter(search, orFilter);

			search.setDisjunction(true);
			search.getFilters().addAll(filters);
		}
	}

	public static void mergeFiltersOr(IMutableSearch search, Filter... filters) {
		mergeFiltersOr(search, Arrays.asList(filters));
	}

	public static void mergeFieldsBefore(IMutableSearch search, List<Field> fields) {
		List<Field> list = search.getFields();
		if (list == null) {
			list = new ArrayList();
			search.setFields(list);
		}
		list.addAll(0, fields);
	}

	public static void mergeFieldsBefore(IMutableSearch search, Field... fields) {
		mergeFieldsBefore(search, Arrays.asList(fields));
	}

	public static void mergeFieldsAfter(IMutableSearch search, List<Field> fields) {
		List<Field> list = search.getFields();
		if (list == null) {
			list = new ArrayList();
			search.setFields(list);
		}
		list.addAll(fields);
	}

	public static void mergeFieldsAfter(IMutableSearch search, Field... fields) {
		mergeFieldsAfter(search, Arrays.asList(fields));
	}

	public static int calcFirstResult(ISearch search) {
		return (search.getPage() > 0) && (search.getMaxResults() > 0) ? search.getPage() * search.getMaxResults()
				: search.getFirstResult() > 0 ? search.getFirstResult() : 0;
	}

	public static IMutableSearch shallowCopy(ISearch source, IMutableSearch destination) {
		destination.setSearchClass(source.getSearchClass());
		destination.setDistinct(source.isDistinct());
		destination.setDisjunction(source.isDisjunction());
		destination.setResultMode(source.getResultMode());
		destination.setFirstResult(source.getFirstResult());
		destination.setPage(source.getPage());
		destination.setMaxResults(source.getMaxResults());
		destination.setFetches(source.getFetches());
		destination.setFields(source.getFields());
		destination.setFilters(source.getFilters());
		destination.setSorts(source.getSorts());

		return destination;
	}

	public static IMutableSearch shallowCopy(ISearch source) {
		return shallowCopy(source, new Search());
	}

	public static <T extends IMutableSearch> T copy(ISearch source, T destination) {
		shallowCopy(source, destination);

		ArrayList<String> fetches = new ArrayList();
		fetches.addAll(source.getFetches());
		destination.setFetches(fetches);

		ArrayList<Field> fields = new ArrayList();
		fields.addAll(source.getFields());
		destination.setFields(fields);

		ArrayList<Filter> filters = new ArrayList();
		filters.addAll(source.getFilters());
		destination.setFilters(filters);

		ArrayList<Sort> sorts = new ArrayList();
		sorts.addAll(source.getSorts());
		destination.setSorts(sorts);

		return destination;
	}

	public static IMutableSearch copy(ISearch source) {
		return copy(source, new Search());
	}

	public static boolean equals(ISearch search, Object obj) {
		if (search == obj) {
			return true;
		}
		if (!(obj instanceof ISearch)) {
			return false;
		}
		ISearch s = (ISearch) obj;
		if (search.getSearchClass() == null ? s.getSearchClass() != null
				: !search.getSearchClass().equals(s.getSearchClass())) {
			return false;
		}
		if ((search.isDisjunction() != s.isDisjunction()) || (search.getResultMode() != s.getResultMode())
				|| (search.getFirstResult() != s.getFirstResult()) || (search.getPage() != s.getPage())
				|| (search.getMaxResults() != s.getMaxResults())) {
			return false;
		}
		if (search.getFetches() == null ? s.getFetches() != null : !search.getFetches().equals(s.getFetches())) {
			return false;
		}
		if (search.getFields() == null ? s.getFields() != null : !search.getFields().equals(s.getFields())) {
			return false;
		}
		if (search.getFilters() == null ? s.getFilters() != null : !search.getFilters().equals(s.getFilters())) {
			return false;
		}
		if (search.getSorts() == null ? s.getSorts() != null : !search.getSorts().equals(s.getSorts())) {
			return false;
		}
		return true;
	}

	public static int hashCode(ISearch search) {
		int hash = 1;
		hash = hash * 31 + (search.getSearchClass() == null ? 0 : search.getSearchClass().hashCode());
		hash = hash * 31 + (search.getFields() == null ? 0 : search.getFields().hashCode());
		hash = hash * 31 + (search.getFilters() == null ? 0 : search.getFilters().hashCode());
		hash = hash * 31 + (search.getSorts() == null ? 0 : search.getSorts().hashCode());
		hash = hash * 31 + (search.isDisjunction() ? 1 : 0);
		hash = hash * 31 + new Integer(search.getResultMode()).hashCode();
		hash = hash * 31 + new Integer(search.getFirstResult()).hashCode();
		hash = hash * 31 + new Integer(search.getPage()).hashCode();
		hash = hash * 31 + new Integer(search.getMaxResults()).hashCode();

		return hash;
	}

	public static String toString(ISearch search) {
		StringBuilder sb = new StringBuilder("Search(");
		sb.append(search.getSearchClass());
		sb.append(")[first: ").append(search.getFirstResult());
		sb.append(", page: ").append(search.getPage());
		sb.append(", max: ").append(search.getMaxResults());
		sb.append("] {\n resultMode: ");
		switch (search.getResultMode()) {
		case 0:
			sb.append("AUTO");
			break;
		case 1:
			sb.append("ARRAY");
			break;
		case 2:
			sb.append("LIST");
			break;
		case 3:
			sb.append("MAP");
			break;
		case 4:
			sb.append("SINGLE");
			break;
		default:
			sb.append("**INVALID RESULT MODE: (" + search.getResultMode() + ")**");
		}
		sb.append(",\n disjunction: ").append(search.isDisjunction());
		sb.append(",\n fields: { ");
		appendList(sb, search.getFields(), ", ");
		sb.append(" },\n filters: {\n  ");
		appendList(sb, search.getFilters(), ",\n  ");
		sb.append("\n },\n sorts: { ");
		appendList(sb, search.getSorts(), ", ");
		sb.append(" }\n}");

		return sb.toString();
	}

	private static void appendList(StringBuilder sb, List<?> list, String separator) {
		if (list == null) {
			sb.append("null");
			return;
		}
		boolean first = true;
		for (Object o : list) {
			if (first) {
				first = false;
			} else {
				sb.append(separator);
			}
			sb.append(o);
		}
	}

	public static <T> List<T> walkList(List<T> list, ItemVisitor<T> visitor, boolean removeNulls) {
		if (list == null) {
			return null;
		}
		ArrayList<T> copy = null;

		int i = 0;
		for (T item : list) {
			T result = visitor.visit(item);
			if ((result != item) || ((removeNulls) && (result == null))) {
				if (copy == null) {
					copy = new ArrayList(list.size());
					copy.addAll(list);
				}
				copy.set(i, result);
				item = result;
			}
			i++;
		}
		if (copy != null) {
			if (removeNulls) {
				for (int j = copy.size() - 1; j >= 0; j--) {
					if (copy.get(j) == null) {
						copy.remove(j);
					}
				}
			}
			return copy;
		}
		return list;
	}

	public static class ItemVisitor<T> {
		public T visit(T item) {
			return item;
		}
	}

	public static List<Filter> walkFilters(List<Filter> filters, FilterVisitor visitor, boolean removeNulls) {
		return walkList(filters, new FilterListVisitor(visitor, removeNulls), removeNulls);
	}

	private static final class FilterListVisitor extends SearchUtil.ItemVisitor<Filter> {
		private SearchUtil.FilterVisitor visitor;
		private boolean removeNulls;

		public FilterListVisitor(SearchUtil.FilterVisitor visitor, boolean removeNulls) {
			this.visitor = visitor;
			this.removeNulls = removeNulls;
		}

		public Filter visit(Filter filter) {
			return SearchUtil.walkFilter(filter, this.visitor, this.removeNulls);
		}
	}

	public static Filter walkFilter(Filter filter, FilterVisitor visitor, boolean removeNulls) {
		filter = visitor.visitBefore(filter);
		if (filter != null) {
			if (filter.isTakesSingleSubFilter()) {
				if ((filter.getValue() instanceof Filter)) {
					Filter result = walkFilter((Filter) filter.getValue(), visitor, removeNulls);
					if (result != filter.getValue()) {
						filter = new Filter(filter.getProperty(), result, filter.getOperator());
					}
				}
			} else if ((filter.isTakesListOfSubFilters()) && ((filter.getValue() instanceof List))) {
				List<Filter> result = walkFilters((List) filter.getValue(), visitor, removeNulls);
				if (result != filter.getValue()) {
					filter = new Filter(filter.getProperty(), result, filter.getOperator());
				}
			}
		}
		filter = visitor.visitAfter(filter);

		return filter;
	}

	public static class FilterVisitor {
		public Filter visitBefore(Filter filter) {
			return filter;
		}

		public Filter visitAfter(Filter filter) {
			return filter;
		}
	}
}
